const paramSymbol = ':'
const paramKeyword = `${paramSymbol}param`

export class TrieNode<T> {
  children: Record<string, TrieNode<T>> = {}

  isEndOfWord = false

  handler: T | null = null

  paramKey: string | null = null

  constructor(
    param: {
      children?: Record<string, TrieNode<T>>
      isEndOfWord?: boolean
      handler?: T | null
      paramKey?: string | null
    } = {},
  ) {
    type K = keyof typeof param
    Object.keys(param).forEach((key) => {
      const v = param[key as K]
      if (v) {
        const that = this as any
        that[key] = v
      }
    })
  }
}

export default class Trie<T> {
  #root: TrieNode<T> = new TrieNode<T>()

  insert(word: string, handler: T) {
    let node = this.#root
    word.split('/').forEach((w) => {
      let paramKey: string | null = null
      if (w[0] === paramSymbol) {
        paramKey = w.substring(1)
        // eslint-disable-next-line no-param-reassign
        w = paramKeyword
      }
      if (!node.children[w]) {
        node.children[w] = new TrieNode({ paramKey })
      }
      node = node.children[w]
    })
    node.isEndOfWord = true
    node.handler = handler
  }

  search(word: string): [TrieNode<T> | null, Record<string, string>] {
    let node = this.#root
    const params: Record<string, string> = {}
    const words = word.split('/')
    for (const w of words) {
      if (!node.children[w]) {
        if (node.children[paramKeyword]) {
          node = node.children[paramKeyword]
          if (node.paramKey) {
            params[node.paramKey] = w
          }
        } else {
          return [null, {}]
        }
      } else {
        node = node.children[w]
      }
    }
    if (node.isEndOfWord) {
      return [node, params]
    }
    return [null, {}]
  }
}
